Music-X Magic: Soup Up Your Sequencer With ARexx Copyright 1994 by Daniel J. Barrett Email: dbarrett@ora.com *** This article may be freely distributed, as long as it *** *** is distributed in its entirety, without modification. *** After a several year wait between updates, Music-X 2.0 finally arrived last Spring. One of the significant changes is that the program now supports ARexx, the Amiga's inter-program communication language. This is very good news: you can now extend Music-X's capabilities in powerful ways by writing "scripts" that operate on your sequences. For example, scripts can count the number of musical events in a sequence, transpose your music in various ways, or perform algorithmic composition. You can even make Music-X communicate with other ARexx compatible programs (more than 400 exist) to increase your power even further. For example, you can send data from Music-X directly into Deluxe Music 2.0 for musical notation, or communicate with a statistical package to analyze your sequences. Unfortunately, the ARexx documentation for Music-X is very sketchy. The manual is brief, omits some vital facts, and the example scripts mentioned in the manual (page 22) were omitted from the program. This article clarifies the manual, reveals the missing information, and teaches you how to write several useful scripts. This article assumes that you have at least looked at the ARexx chapter of the upgrade manual; keep it nearby for reference. A MOTIVATING EXAMPLE Let's begin with a simple script that transposes all the notes in your sequence upward by a perfect fifth (7 semitones). The numbers 1-12 on the left side are not part of the script; they are for our reference only. 1 /* Transpose up by a perfect fifth. */ 2 OPTIONS RESULTS 3 BeginScan ALL 4 NextEvent 5 DO WHILE RESULT = 1 6 IF Event.Type = "NOTE" THEN DO 7 Event.Num = Event.Num + 7 8 ReplEvent 9 END 10 NextEvent 11 END 12 EndScan Let's examine what each line means. 1. A comment. Every ARexx script must begin with a comment; this is required, or else the script will not work. 2. Tell ARexx to report the results of certain commands in a variable called "RESULT". We need this variable on line 5. 3. Tell Music-X that we want to examine (scan through) all the events in the current sequence. 4. Read the first event. Its data is automatically stored in a special variable called "Event". 5-11. This "loop" is the heart of the script. As each event is read, its type is checked. If it is a NOTE event, add 7 to its note number, and use "ReplEvent" to make the change permanent. No matter what, we move to the next event and repeat. When there are no more events, "NextEvent" will fail, RESULT will equal zero (meaning failure), and the loop ends. 12. Tell Music-X we are done scanning the sequence. Type this script into a file called "Transposer.mxe". Now load a sequence into the Bar Editor, choose "Rexx..." from the Modules menu, use the file requestor to locate your Transposer.mxe script, and double-click on the script name. Voila! Your notes have been transposed. BASIC CONCEPTS ARexx support in Music-X is handled by a new module called RexxEdit. This module is automatically installed when you use the Music-X installation program. However, due to a bug in the installation, all your modules may not be accessible due to incorrect path names. If you encounter this, then exit Music-X, run the "Install Modules" program, click on each module name and remove the leading colon ":" from its Path, choose "Save", and restart Music-X. In a nutshell, RexxEdit allows you to do four kinds of operations: 1. Examine, add, delete, and change data in sequences. 2. Display requestors with a variety of gadgets for user input. 3. Get information about the bar editor: grid size, selected regions, step size, etc. 4. Communicate with other ARexx-compatible programs. In addition, RexxEdit does NOT allow you to: 1. Control recording and playback. 2. Control the Tool List in the Bar and Event Editors. 3. Use the cut, copy, and paste features. 4. Display or hide events in the Bar and Event Editors. 5. Affect the controls on the Sequencer, Filters, Samples, and Librarian pages. Perhaps a later version of Music-X will support such operations; but given how long it took for the 2.0 update, I have my doubts. OPERATIONS ON SEQUENCES Scripts that operate on sequences typically look a lot like our "Transposer.mxe" example. First, BeginScan must be called to examine the sequence. This is followed by a loop which examines each event using NextEvent and processes it in some way. When NextEvent returns "0" in the RESULT variable, the loop is finished, and we call EndScan. If you plan to add events to a sequence using AddEvent, there is an undocumented step which you MUST do: initialize the "Event" variable. This is done by placing the line Event. = "" once in your script, before any call to AddEvent. Note that the period after "Event" is mandatory because it is a stem variable. It doesn't matter what value you assign here; for example, Event. = 192873 will work equally well. As an example, let's modify Transposer.mxe so instead of moving the notes, it adds new notes 7 semitones above the originals. For example, every C will get a new G added above it. This requires two changes: initialize the "Event" variable (say, between lines 2 and 3), and change the ReplEvent on line 8 to AddEvent. Store the script in a new file called Interval.mxe and test it out. REQUESTORS AND USER INPUT Our Transposer.mxe script is useful but limited. What if you want to transpose notes by sevenths instead of fifths? Do we need a whole new script for every kind of transposition? Nope! In this section, we will add a requestor to Transposer.mxe with several gadgets, allowing user control over the script's behavior. RexxEdit can display three kinds of requestors: a file requestor (MXFile), a "tell the user" requestor (MXReport), and a gadget-filled requestor for user input (MXRequest). The first two are well explained on page 46 of the upgrade manual. Our example will use the more complicated MXRequest. There are four steps to using it: 1. Decide whether you want a 1-column or 2-column requestor, using the MXColumn command. (Optional.) 2. Define your gadgets. Your choices are: MXButton On/Off buttons. MXSlider Sliders with positive values. MXMirror Sliders with negative and positive values. MXString String gadgets for text input. MXNoteSize The Music-X note duration control. MXLabel Plain text; not for user input. 3. Display the requestor with the MXRequest command. 4. Read the gadgets with the MXValue command to find out the user's selections. Our example lets the user choose a transposition interval, whether to replace or add notes, and whether to affect all notes or just selected/marked notes. First, let's design the requestor. 1 /* A requestor for transposing. */ 2 OPTIONS RESULTS 3 MXMirror "Semitones:,127" 4 MXRadio "Replace existing notes,Add new notes" 5 MXRadio "All notes,Selected notes only" 6 MXRequest "My Transposer" Lines 3-5 define three gadgets: a "Semitones" slider running from -127 to +127, a pair of buttons for choosing replace or add, and another pair of buttons for choosing all or selected notes. Line 6 actually displays the requestor with our gadgets, as shown in Figure 1. To display it yourself, save lines 1-5 in a script called "Requestor.mxe" and execute it. Play around with the gadgets all you like, but remember that this requestor will have no actual effect on your sequence... yet. Continuing, let's find out what the user chose to do. If the user clicked CANCEL, the RESULT variable will have value 0. If so, we choose to exit immediately: 7 IF RESULT = 0 THEN 8 EXIT Next, we check our three defined gadgets using MXValue. To read the first gadget, use "MXValue 1"; for the second, "MXValue 2"; etc. The gadget value is returned in the RESULT variable. For "Semitones" slider, we store the results in a variable "semitones": 9 MXValue 1 10 semitones = RESULT The "add/replace" radio buttons have value 0 if the first button was chosen, or a 1 for the second button: 11 MXValue 2 12 add = RESULT For the "all/selected events" buttons, we set the variable "scanType" to "ALL" or "SELECTED" appropriately. These words have special value for the BeginScan command later: 13 MXValue 3 14 IF RESULT = 0 THEN 15 scanType = "ALL" 16 ELSE 17 scanType = "SELECTED" Save lines 1-17 in a file called "Requestor2.mxe", execute it, play with the gadgets, and see what happens. Now we are done with gadgets and user input, so it's time to do the actual transposition. 18 Event. = "" 19 BeginScan scanType 20 NextEvent 21 DO WHILE RESULT = 1 22 IF Event.Type = "NOTE" THEN DO 23 Event.Num = Event.Num + semitones 24 IF add = 1 THEN 25 AddEvent 26 ELSE 27 ReplEvent 28 END 29 NextEvent 30 END Note how similar this is to the loop in Transposer.mxe. The only differences are in lines: 18. Initialize the Event variable so we can add events. 19. BeginScan is instructed to scan ALL or SELECTED events, based on the user choice. 23. We now add the value "semitones" instead of 7. 24-27. Depending on the user's choice, we either add or replace the notes we transpose. Put lines 1-30 in a file called NewTransposer.mxe, and have fun with this highly useful script! If you try to transpose notes higher than MIDI allows (127), Music-X will "wrap then around" to 0 again; similarly for note numbers below 0. A note of caution: this script has very little error checking. You really should check the value of RESULT after the BeginScan, AddEvent, and ReplEvent commands to make sure they worked. EXAMINING THE BAR EDITOR In this section, we will build a script for adding pitch bend to a marked or selected region. Pitch bend events will be added, one at a time, at regular intervals in the region. The user may define how far apart the pitch bend events are. When followed by Music-X's Sculpt tool, this script will let you create interesting pitch-bend shapes. Bar Editor information is examined by using the GetBarData command. This command fills in the variable "BarData" with information like the current grid size, measure size, beginning and end of a marked region, etc. GetBarData is used like this: 1 /* Bar Data example with pitch bend events. */ 2 OPTIONS RESULTS 3 GetBarData 4 IF RESULT = 0 THEN DO 5 MXReport "Could not get bar data." 6 EXIT 7 END Next, we detect whether there is a region defined. 8 IF BarData.Select = "NONE" THEN DO 9 MXReport "No region is marked/selected." 10 EXIT 11 END Save lines 1-11 to a file called "CheckRegion.mxe" and execute it. If no region is marked or selected, the script will display the MXReport requestor. Next, we allow the user to choose the spacing between the pitch bend events, using an MXNoteSize gadget, and the MIDI channel for the events, using a slider gadget. We store the user's choices in the variables "spacing" and "midiChannel", respectively. 12 MXNoteSize "Set Spacing:" 13 MXSlider "MIDI Channel:,1,16,1" 14 MXRequest "Add Pitch Bend Events" 15 IF RESULT = 0 THEN 16 EXIT 17 MXValue 1 18 spacing = RESULT 19 MXValue 2 20 midiChannel = RESULT - 1 The reason for "RESULT - 1" is that Music-X stores MIDI channels 1-16 as 0-15 internally. Add lines 12-20 to your script so you can see the requestor in Figure 2. Next, we locate the beginning and end of the region. Time in Music-X is measured in bars (measures) and clocks (clock ticks in each measure). For example, a note might appear in bar 17, clock 2475. Information about bars and clocks is stored in the variables: BarData.Start.Bars Measure number of the start of the region. BarData.Start.Clocks Clock number of the start of the region. BarData.Stop.Bars Measure number of the end of the region. BarData.Stop.Clocks Clock number of the end of the region. BarData.Measure Number of clocks per measure. We use our own variable "currentTime" to represent the current location in the region as we add events. As the script progresses, we repeatedly add a number of clocks (stored in the "spacing" variable) to the current time. However, when adding an event, we need to know the Bars and Clocks values, which will require a little math. Given the measure number and clock number of a Music-X event, the formula for calculating its location in clocks is: (Measure number) * (Clocks per Measure) + (Clock number) Thus, in our script, we compute the starting and ending times of our region, in clocks: 21 currentTime = BarData.Start.Bars * BarData.Measure , + BarData.Start.Clocks 22 endTime = BarData.Stop.Bars * BarData.Measure , + BarData.Stop.Clocks (Note: In ARexx, a comma means "continued on the next line." If you can fit each command on one line -- not possible on this magazine page -- then you must omit the commas.) Next, we prepare to scan the region. Just for fun, while the script is working, we change the pointer to the "sleeping cloud" to indicate work is being done. 23 MXPointer "SLEEPY" 24 Event. = "" /* Initialize Event variable */ 25 BeginScan Since we are adding pitch bend events, we can fill in some of the Event variable fields that will remain constant: the event type, the MIDI channel, and an event value (we choose +8192, which is zero pitch bend). 26 Event.Type = "PBEN" 27 Event.Channel = midiChannel 28 Event.Num = 8192 Finally, it is time to add pitch bend events! To add them at the right locations, we must calculate the Bar and Clock numbers where they should be added. The bar number is obtained using integer division (the "%" operator), and the clock number using the "mod" or "remainder" function (the "//" operator): 29 DO WHILE currentTime <= endTime 30 Event.Start.Bars = currentTime % BarData.Measure 31 Event.Start.Clocks = currentTime // BarData.Measure 32 AddEvent 33 currentTime = currentTime + spacing 34 END When the loop is done, clean up and exit. 35 EndScan 36 MXPointer "NORMAL" Save lines 1-36 in a file called "PitchBend.mxe". To use this script, first mark a region or select some events. Second, run the PitchBend.mxe script. Third, use the Sculpt Tool to shape the pitch bend events the way you want them. Enjoy! Note: RexxEdit has a bug concerning marked regions. If a region appears to be marked, but you keep getting the requestor "No region is marked/selected," try marking the region again or using the Select tool instead of Mark. (See "TECH TALK" below for more information.) Another note: the above assumes you are using "relative time" sequences, not "absolute time" sequences. If you use absolute time sequences, then time is measured in quarter frames instead of clocks; see the manual for more information. COMMUNICATING WITH OTHER PROGRAMS Using the ARexx "ADDRESS" command, Music-X scripts can talk to other ARexx-compatible products such as Deluxe Music 2.0, Bars and Pipes Professional, One Stop Music Shop, and even non-music products. How about using SuperBase Professional to store your Music-X sequences? VLT for uploading and downloading sequences? AmigaVision for multimedia? The possibilities are numerous and exciting. Communicating with another program has 4 steps. 1. Find out the ARexx port names of the other program, by consulting its documentation. 2. Store Music-X's port name in a variable for safekeeping, using the Address() command. (See "TECH TALK", below, if you are interested in the reason.) 3. Use the Show("P") command to make sure that the other programs are running. If not, then your script run them, using the "WaitForPort" command to wait until the program is running. 4. Use the "ADDRESS" command to talk to the other programs. In this section, we write scripts to communicate with Electronic Arts' Deluxe Music 2.0 notation program, port name "DMUSIC", and Oxxi's TurboText editor, port name "TURBOTEXT". Our first script sends the currently selected Music-X note to Deluxe Music, inserting it at Deluxe Music's current cursor location. First, we save Music-X's port name in a variable "myAddress": 1 /* Example script using Deluxe Music 2.0. */ 2 OPTIONS RESULTS 3 myAddress = Address() Next, we make sure that something is selected. 4 GetBarData 5 IF BarData.Select ~= "SELECT" THEN DO 6 MXReport "Nothing is selected." 7 EXIT 8 END Next, we make sure that the selected event is a note. 9 BeginScan "SELECTED" 10 NextEvent 11 EndScan 12 IF Event.Type ~= "NOTE" THEN DO 13 MXReport "You must select a note." 14 EXIT 15 END Next, we make sure Deluxe Music is running by looking for its ARexx port. If a port named "DMUSIC" does not exist, then we run Deluxe Music. 16 IF ~Show('P', 'DMUSIC') THEN DO 17 ADDRESS COMMAND 18 "Run < NIL: > NIL: DMusic" 19 WaitForPort "DMUSIC" 20 END Now, we are ready to send the note from Music-X to Deluxe Music. 21 ADDRESS "DMUSIC" 22 INSERTITEM NOTE PITCH Event.Num 23 NEXT NOTE Finally, we pop the Deluxe Music screen to the front to see our handiwork: 24 SCREENTOFRONT Save lines 1-24 to a file called "DMusic.mxe", execute the script, and watch what happens. With some work, this script could be the basis for a whole direct interface between Music-X and Deluxe Music. You may notice that we didn't use the myAddress variable. This is because the script did all its Music-X communication first, and then all its Deluxe Music communication. The next example is more complex, sending information about notes and their velocities from a sequence into a TurboText edit window. We start off with the usual, saving Music-X's address and invoking TurboText if it is not running: 1 /* Send note information to TurboText. */ 2 OPTIONS RESULTS 3 myAddress = Address() 4 IF ~Show("P", "TURBOTEXT") THEN DO 5 ADDRESS COMMAND 6 "TTX > NIL: BACKGROUND NOWINDOW" 7 WaitForPort "TURBOTEXT" 8 END Next, we tell TurboText to open a new edit window. The name of that window (its own ARexx port) is returned in the RESULT variable: 9 ADDRESS "TURBOTEXT" 10 OpenDoc 11 windowName = RESULT Finally, we talk to Music-X again, scan through the sequence, picking out notes, and sending their information to the TurboText window: 12 ADDRESS VALUE myAddress 13 BeginScan ALL 14 NextEvent 15 DO WHILE RESULT = 1 16 IF Event.Type = "NOTE" THEN DO 17 ADDRESS VALUE windowName 18 Insert "Note" Event.Num "Vel" Event.Attack 19 InsertLine 20 ADDRESS VALUE myAddress 21 END 22 NextEvent 23 END 24 EndScan I realize that these Deluxe Music and TurboText scripts are harder to understand than the previous ones. This is because each program has its own set of commands, such as "OpenDoc" and "Insert" in the TurboText example. Writing scripts that connect two programs means learning the command sets for both programs, and this may require careful reading of each program's documentation, as well as a lot of hair pulling! REXXEDIT TIPS AND TRICKS Here is a set of (mostly undocumented) tips and tricks I've discovered while writing Music-X scripts. o The MXReport command is great for helping to debug your scripts. If you want to see the value of a variable (say, "myVar") at any time, just insert the line "MXReport myVar" at the appropriate spot in the script. If you need more space for messages, use MXLabel and MXRequest. o If your gadgets in an MXRequest don't have enough space between them for your tastes, the command MXLabel "" will insert a blank line. Remember to count this MXLabel command when using MXValue to read gadget values! o By default, the RexxEdit module is available only inside the Bar and Event Editors. But you can use the "Install Modules" script to make it available in other pages. Simply click on the other checkboxes in the "Install Modules" window, quit and restart Music-X, and you're set. The ARexx functionality will be more limited on other pages, because there is no "current sequence" being displayed, but you can still do some interesting things. o The "Event" variable has different fields depending on what type of event it contains. For example, a NOTE event has its velocity in Event.Attack, but it does not use Event.Pressure. You must write your scripts carefully so they examine ONLY the Event fields that apply to the current event. Read pages 49-52 carefully to learn which event fields are valid. (See also the next item.) o The spacing and boldfacing on pages 49 and 50 of the Music-X 2.0 Upgrade Manual is messed up, making it difficult to distinguish event types from variables. Four variable names are incorrectly boldfaced: EVENT.DEM belongs to the TSIG event, EVENT.TRANSPOSE and EVENT.CUT belong to the PSEQ event, and EVENT.ATTACK belongs to the NOTE event. o The range of pitch bend values goes from -8192 to +8191, with 0 being zero pitch bend. PBEN event values (Event.Num) are really between 0 and 16383; that is, 8192 higher than the actual bend value. If you use an MXMirror gadget to get pitch bend values from the user, remember to add 8192 to the result before storing it in Event.Num. o Since pitch bend goes from -8192 to +8191, not +8192. If you use an MXMirror slider for pitch bend, the highest positive value +8192 is invalid. So your script may need code like this: MXValue 3 /* Get the MXMirror value. */ value = RESULT IF value = 8192 THEN /* Disallow +8192 value. */ value = 8191 REXXEDIT PROBLEMS One of RexxEdit's omissions is that it has no ARexx command for disabling user input. While a script is running, all gadgets on the Music-X screen are "live," even though they do not appear to respond, and they will react to all of your inadvertent keystrokes and mouseclicks when the script finishes. So, while a script is executing, don't click on any gadgets except those in an ARexx requestor (e.g., MXRequest). As a visual aid, do "MXPointer SLEEPY" while a script is running. Second, RexxEdit cannot abort a script in progress. Suppose your script scans a sequence with a loop, but you forget to call NextEvent in it. You now have an infinite loop and no way to stop it except by rebooting your Amiga. Third, as mentioned earlier, Music-X "forgets" any marked region every time RexxEdit is run. This is especially confusing because the region on the screen still appears to be marked. The problem does not occur with selected events. To get around this problem, either use selecting instead of marking when possible, or re-mark the region after each ARexx macro is run. There is also a problem with the selected events. The Select tool works fine. However, if you use the Move or Add tools and click on an event, it highlights as if it were selected. Other modules like the Quantizer will treat it as selected. However, RexxEdit does not think it is selected. The moral: if a script requires selected events, you must use the Select tool. TECH TALK If you've used ARexx before, you may notice that Music-X's interface is unusual in several notable ways. First, its ARexx port name is completely undocumented. By using the command MXReport Address() I discovered that the name is "MUSIC-X AREXX EDIT". However, this information is of no use, because of a second non-standard feature: it's impossible to control Music-X from another program. All scripts must be run from inside Music-X. This is because the ARexx port belongs to RexxEdit, not to Music-X. When RexxEdit is not running, the port disappears, and when RexxEdit is running, Music-X occupies its full attention. A third oddity involves ARexx return codes. By convention, ARexx commands return their success (1) or failure (0) values in the "RC" variable. Music-X uses the "RESULT" variable instead, which is usually reserved for more complex return values. RC is unused. Until you figure this out, you'll be scratching your head wondering why your error-checking doesn't work. GOOD LUCK! By using ARexx and RexxEdit, you can personalize Music-X with powerful new tools of your own invention. It takes some effort to get used to ARexx, but the results can really be worth it. === Daniel J. Barrett has been making electronic and computer music since 1979, and has been an Amiga owner since 1987.